#include "RendererBase.h"
#include "RenderListBase.h"

RendererBase::~RendererBase()
{
	Cleanup();
}

void RendererBase::Cleanup()
{
	_refreshables.clear();
	ResetValues(false);

	for (auto it = _workers.begin(); it != _workers.end(); ++it)
		delete *it;

	delete Renderers;
}

bool RendererBase::CullObject(const Point3i &px)
{
	return false;
}

static const Point3i _vecOffsets[] =
{
	Point3i(0, 0, -1),
	Point3i(1, 0, 0),
	Point3i(0, 0, 1),
	Point3i(-1, 0, 0),
	Point3i(0, -1, 0),
	Point3i(0, 1, 0)
};

bool RendererBase::CullBlockSide(const RenderBox &box, const byte &faceSide)
{
	bool isFullBlock = GetSchematic()->IsFullBlockAt(box.Position.X, box.Position.Y, box.Position.Z);
	bool isOpaque = GetSchematic()->IsOpaqueBlockAt(box.Position.X, box.Position.Y, box.Position.Z);

	if (isFullBlock)
	{
		auto offX = box.Position.X + _vecOffsets[faceSide].X, offY = box.Position.Y + _vecOffsets[faceSide].Y, offZ = box.Position.Z + _vecOffsets[faceSide].Z;
		const Block &b = GetSchematic()->GetBlock(offX, offY, offZ);
		
		if (!CullObject(Point3i(offX, offY, offZ)) &&
			!b.IsEmpty() &&
			IsFullBlockAt(offX, offY, offZ) &&
			isOpaque == GetSchematic()->IsOpaqueBlockAt(offX, offY, offZ))
			return true;
	}

	return false;
}

void RendererBase::CreateWorker()
{
	RendererWorker *worker = new RendererWorker(this);
	worker->CreateThread();
	worker->GetThread()->Run();
	_workers.push_back(worker);
}

void RendererBase::ResetValues(bool reinit)
{
	if (_textures != nullptr)
	{
		// stop all the workers
		for (size_t i = 0; i < _workers.size(); ++i)
		{
			auto &worker = _workers[i];

			worker->Restart();
		}

		for (int i = 0; i < _schematic->TotalChunks; ++i)
		{
			if (_textures[i] != nullptr)
				delete _textures[i];
		}

		delete[] _textures;
		delete[] _textureQueued;

		_textures = nullptr;
		_textureQueued = nullptr;
	}
		
	if (reinit)
	{
		_textures = new PrimitiveRendererBase*[GetSchematic()->TotalChunks];
		_textureQueued = new byte[GetSchematic()->TotalChunks];

		memset(_textures, 0, sizeof(PrimitiveRendererBase*) * GetSchematic()->TotalChunks);
		memset(_textureQueued, 0, sizeof(byte) * GetSchematic()->TotalChunks);
		ChunksRendered = 0;
	}
}

RendererWorker *RendererBase::GetMostFreeWorker()
{
	if (_workers.size() == 1)
		return _workers[0];

	size_t lowestCount = UINT_MAX;
	size_t mostFree = -1;

	for (size_t i = 0; i < _workers.size(); ++i)
	{
		auto &worker = _workers[i];
			
		wxMutexLocker locker(worker->ChunksAccessLock);
		auto s = worker->ChunksToRender.size();

		if (s == 0)
		{
			mostFree = i;
			break;
		}

		if (s < lowestCount)
		{
			lowestCount = s;
			mostFree = i;
		}
	}

	return _workers[mostFree];
}

void RendererBase::PreloadChunk(const int &x, const int &z)
{
	auto worker = GetMostFreeWorker();

	wxMutexLocker locka(worker->ChunksAccessLock);
	worker->ChunksToRender.push(Point2i(x, z));

	wxMutexLocker lockb(worker->WorkerLock);
	worker->WorkerCondition.Broadcast();
}

// This is probably slow as poop. Figure out a better way of doing this.
void RendererBase::PreloadChunks(const bool &boundsChanged)
{
	if (GetSchematic() != nullptr)
	{
		for (int z = 0; z < GetSchematic()->LengthInChunks; ++z)
		{
			for (int x = 0; x < GetSchematic()->WidthInChunks; ++x)
			{
				auto pos = (z * GetSchematic()->WidthInChunks) + x;
				auto tex = _textures[pos];

				if (_textureQueued[pos] == 0)
				{
					if (tex == nullptr)
					{
						_textureQueued[pos] = 1;
						PreloadChunk(x, z);
					}
					else if (boundsChanged)
					{
						_textureQueued[pos] = 2;
						PreloadChunk(x, z);
					}
				}
			}
		}
	}
}

void GetAdjacentBlocks(const Point3i &position, const byte &faceIndex, Point3i offsets[8])
{
	switch (faceIndex)
	{
	case 5:
		offsets[0] = Point3i(position.X - 1, position.Y + 1, position.Z);
		offsets[1] = Point3i(position.X, position.Y + 1, position.Z - 1);
		offsets[2] = Point3i(position.X + 1, position.Y + 1, position.Z);
		offsets[3] = Point3i(position.X, position.Y + 1, position.Z + 1);

		offsets[4] = Point3i(position.X - 1, position.Y + 1, position.Z - 1);
		offsets[5] = Point3i(position.X + 1, position.Y + 1, position.Z - 1);
		offsets[6] = Point3i(position.X + 1, position.Y + 1, position.Z + 1);
		offsets[7] = Point3i(position.X - 1, position.Y + 1, position.Z + 1);
		break;
	case 4:
		offsets[0] = Point3i(position.X + 1, position.Y - 1, position.Z);
		offsets[1] = Point3i(position.X, position.Y - 1, position.Z - 1);
		offsets[2] = Point3i(position.X - 1, position.Y - 1, position.Z);
		offsets[3] = Point3i(position.X, position.Y - 1, position.Z + 1);

		offsets[4] = Point3i(position.X + 1, position.Y - 1, position.Z - 1);
		offsets[5] = Point3i(position.X - 1, position.Y - 1, position.Z - 1);
		offsets[6] = Point3i(position.X - 1, position.Y - 1, position.Z + 1);
		offsets[7] = Point3i(position.X + 1, position.Y - 1, position.Z + 1);
		break;
	case 0:
		offsets[0] = Point3i(position.X + 1, position.Y, position.Z - 1);
		offsets[1] = Point3i(position.X, position.Y + 1, position.Z - 1);
		offsets[2] = Point3i(position.X - 1, position.Y, position.Z - 1);
		offsets[3] = Point3i(position.X, position.Y - 1, position.Z - 1);

		offsets[4] = Point3i(position.X + 1, position.Y + 1, position.Z - 1);
		offsets[5] = Point3i(position.X - 1, position.Y + 1, position.Z - 1);
		offsets[6] = Point3i(position.X - 1, position.Y - 1, position.Z - 1);
		offsets[7] = Point3i(position.X + 1, position.Y - 1, position.Z - 1);
		break;
	case 1:
		offsets[0] = Point3i(position.X + 1, position.Y, position.Z + 1);
		offsets[1] = Point3i(position.X + 1, position.Y + 1, position.Z);
		offsets[2] = Point3i(position.X + 1, position.Y, position.Z - 1);
		offsets[3] = Point3i(position.X + 1, position.Y - 1, position.Z);
										
		offsets[4] = Point3i(position.X + 1, position.Y + 1, position.Z + 1);
		offsets[5] = Point3i(position.X + 1, position.Y + 1, position.Z - 1);
		offsets[6] = Point3i(position.X + 1, position.Y - 1, position.Z - 1);
		offsets[7] = Point3i(position.X + 1, position.Y - 1, position.Z + 1);
		break;
	case 2:
		offsets[0] = Point3i(position.X - 1, position.Y, position.Z + 1);
		offsets[1] = Point3i(position.X, position.Y + 1, position.Z + 1);
		offsets[2] = Point3i(position.X + 1, position.Y, position.Z + 1);
		offsets[3] = Point3i(position.X, position.Y - 1, position.Z + 1);

		offsets[4] = Point3i(position.X - 1, position.Y + 1, position.Z + 1);
		offsets[5] = Point3i(position.X + 1, position.Y + 1, position.Z + 1);
		offsets[6] = Point3i(position.X + 1, position.Y - 1, position.Z + 1);
		offsets[7] = Point3i(position.X - 1, position.Y - 1, position.Z + 1);
		break;
	case 3:
		offsets[0] = Point3i(position.X - 1, position.Y, position.Z - 1);
		offsets[1] = Point3i(position.X - 1, position.Y + 1, position.Z);
		offsets[2] = Point3i(position.X - 1, position.Y, position.Z + 1);
		offsets[3] = Point3i(position.X - 1, position.Y - 1, position.Z);
										
		offsets[4] = Point3i(position.X - 1, position.Y + 1, position.Z - 1);
		offsets[5] = Point3i(position.X - 1, position.Y + 1, position.Z + 1);
		offsets[6] = Point3i(position.X - 1, position.Y - 1, position.Z + 1);
		offsets[7] = Point3i(position.X - 1, position.Y - 1, position.Z - 1);
		break;
	}
}

bool RendererBase::LightBoxSide(const struct RenderBox &box, const byte &faceIndex, const struct Face &face, Colorb vertexColors[4])
{
	Point3i offsets[8];

	GetAdjacentBlocks(box.Position, faceIndex, offsets);

	float scales[4] =
	{
		1,
		1,
		1,
		1
	};

	const float lowestScale = 0.50f;
	const float scaleVal = (1 - lowestScale) * 0.5f;

	auto &left = GetSchematic()->GetBlock(offsets[0]);
	auto &top = GetSchematic()->GetBlock(offsets[1]);
	auto &right = GetSchematic()->GetBlock(offsets[2]);
	auto &bottom = GetSchematic()->GetBlock(offsets[3]);
	
	if (!left.IsEmpty() && IsFullBlockAt(offsets[0]))
	{
		scales[3] -= scaleVal;
		scales[0] -= scaleVal;
	}

	if (!top.IsEmpty() && IsFullBlockAt(offsets[1]))
	{
		scales[0] -= scaleVal;
		scales[1] -= scaleVal;
	}

	if (!right.IsEmpty() && IsFullBlockAt(offsets[2]))
	{
		scales[1] -= scaleVal;
		scales[2] -= scaleVal;
	}

	if (!bottom.IsEmpty() && IsFullBlockAt(offsets[3]))
	{
		scales[2] -= scaleVal;
		scales[3] -= scaleVal;
	}

	auto &topLeft = GetSchematic()->GetBlock(offsets[4]);
	auto &topRight = GetSchematic()->GetBlock(offsets[5]);
	auto &bottomRight = GetSchematic()->GetBlock(offsets[6]);
	auto &bottomLeft = GetSchematic()->GetBlock(offsets[7]);

	if (!topLeft.IsEmpty() && IsFullBlockAt(offsets[4]))
		scales[0] -= scaleVal;

	if (!topRight.IsEmpty() && IsFullBlockAt(offsets[5]))
		scales[1] -= scaleVal;

	if (!bottomRight.IsEmpty() && IsFullBlockAt(offsets[6]))
		scales[2] -= scaleVal;

	if (!bottomLeft.IsEmpty() && IsFullBlockAt(offsets[7]))
		scales[3] -= scaleVal;

	for (int i = 0; i < 4; ++i)
	{
		if (scales[i] < lowestScale)
			scales[i] = lowestScale;

		vertexColors[i] *= scales[i];
	}

	return true;
}